package client

import (
	"context"
	"github.com/aberic/gnomon"
	"github.com/aberic/gnomon/log"
	"github.com/aberic/proc"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/swarm"
	"github.com/pkg/errors"
	proto "gykjgit.dccnet.com.cn/chain/proto/swarm"
	"io/ioutil"
	"strings"
	"sync"
)

type Resource struct {
	resourceNodes map[string]*proto.ReqContainerSync
	mu            sync.RWMutex
}

func newResource() *Resource {
	return &Resource{resourceNodes: map[string]*proto.ReqContainerSync{}}
}

// Info 获取资源信息
func (r *Resource) ContainerID(hostname string) (containerID string, err error) {
	defer r.mu.RUnlock()
	r.mu.RLock()
	for _, reqContainerSync := range r.resourceNodes {
		if reqContainerSync.Docker.Hostname == hostname {
			for _, cs := range reqContainerSync.Containers {
				if strings.Contains(cs.ServiceName, "proc") {
					return cs.ID, nil
				}
			}
		}
	}
	return "", errors.New("container do not found")
}

// Info 获取资源信息
func (r *Resource) Info(ctx context.Context) (resource *proto.ResourceInfo, err error) {
	if services, err := Obtain().service.List(ctx); nil != err {
		return nil, err
	} else {
		var resourceServices []*proto.Service
		for _, service := range services {
			resourceServices = append(resourceServices, &proto.Service{
				ID:       service.ID,
				CreateAt: service.CreatedAt.String(),
				UpdateAt: service.UpdatedAt.String(),
			})
		}
		return &proto.ResourceInfo{Nodes: r.resourceNodes, Services: resourceServices}, nil
	}
}

func (r *Resource) Proc(pNew *proc.Proc) {

}

func (r *Resource) SyncContainers(ctx context.Context, sync *proto.ReqContainerSync) (resource *proto.Response, err error) {
	var nodes []swarm.Node
	if nodes, err = Obtain().Node().List(ctx); nil != err {
		return nil, err
	}
	for _, node := range nodes {
		if node.ID == sync.Docker.NodeID {
			sync.Docker.CpuTotal = node.Description.Resources.NanoCPUs
		}
	}
	r.mu.Lock()
	r.resourceNodes[sync.Docker.NodeID] = sync
	r.mu.Unlock()
	log.Debug("SyncContainers", log.Field("r.resourceNodes", r.resourceNodes))
	return &proto.Response{Code: proto.Code_success}, nil
}

// StatsAll StatsAll
func (r *Resource) StatsAll4Containers(ctx context.Context) (*proto.ReqContainerSync, error) {
	var (
		dockerInfo types.Info
		containers []types.Container
		err        error
	)
	dockerInfo, err = Obtain().Docker().Info(ctx)
	if nil != err {
		return nil, errors.Wrap(err, "dockerInfo error")
	}
	log.Debug("StatsAll4Containers", log.Field("dockerInfo", dockerInfo))
	containers, err = Obtain().Container().List(ctx)
	if nil != err {
		return nil, errors.Wrap(err, "container list error")
	}
	var (
		cs []*proto.Container
		mu sync.Mutex
		wg sync.WaitGroup
	)
	for _, container := range containers {
		wg.Add(1)
		go func(container types.Container) {
			stat, _, errNew := Obtain().Container().Stats(ctx, container.ID)
			if nil != errNew {
				err = errNew
			} else {
				log.Debug("StatsAll4Containers", log.Field("stat", stat))
				cpuPercent := calculateCPUPercentUnix(stat.PreCPUStats.CPUUsage.TotalUsage, stat.PreCPUStats.SystemUsage, stat)
				memPercent := calculateMemPercentUnixNoCache(stat.MemoryStats.Limit, stat.MemoryStats.Usage)

				defer mu.Unlock()
				mu.Lock()
				cs = append(cs, &proto.Container{
					ID:          container.ID,
					Image:       container.Image,
					ImageID:     container.ImageID,
					NodeID:      container.Labels["com.docker.swarm.node.id"],
					ServiceID:   container.Labels["com.docker.swarm.service.id"],
					ServiceName: container.Labels["com.docker.swarm.service.name"],
					State:       container.State,
					Status:      container.Status,
					Read:        stat.Read.String(),
					CpuCount:    stat.CPUStats.OnlineCPUs,
					CpuUsage:    float32(cpuPercent),
					MemUsage:    float32(memPercent),
				})
			}
			wg.Done()
		}(container)
	}
	wg.Wait()
	if nil != err {
		return nil, errors.Wrap(err, "container stats error")
	}
	var rms []*proto.RemoteManager
	for _, drm := range dockerInfo.Swarm.RemoteManagers {
		rms = append(rms, &proto.RemoteManager{NodeID: drm.NodeID, Addr: drm.Addr})
	}
	var bs []byte
	bs, err = ioutil.ReadFile(gnomon.EnvGet("HOSTNAME"))
	if nil != err {
		return nil, err
	}
	docker := &proto.DockerInfo{
		ID:                dockerInfo.ID,
		Containers:        uint32(dockerInfo.Containers),
		ContainersRunning: uint32(dockerInfo.ContainersRunning),
		ContainersPaused:  uint32(dockerInfo.ContainersPaused),
		ContainersStopped: uint32(dockerInfo.ContainersStopped),
		Images:            uint32(dockerInfo.Images),
		OSType:            dockerInfo.OSType,
		Architecture:      dockerInfo.Architecture,
		CpuCount:          uint32(dockerInfo.NCPU),
		MemTotal:          uint64(dockerInfo.MemTotal),
		NodeID:            dockerInfo.Swarm.NodeID,
		LocalNodeState:    string(dockerInfo.Swarm.LocalNodeState),
		Error:             dockerInfo.Swarm.Error,
		RemoteManagers:    rms,
		Hostname:          gnomon.StringTrim(string(bs)),
	}
	if nil != dockerInfo.Swarm.Cluster {
		docker.CreateAT = dockerInfo.Swarm.Cluster.CreatedAt.String()
		docker.UpdatedAt = dockerInfo.Swarm.Cluster.UpdatedAt.String()
	}
	return &proto.ReqContainerSync{
		Docker:     docker,
		Containers: cs,
	}, nil
}

func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.Stats) float64 {
	var (
		cpuPercent = 0.0
		// calculate the change for the cpu usage of the container in between readings
		cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage - previousCPU)
		// calculate the change for the entire system between readings
		systemDelta = float64(v.CPUStats.SystemUsage - previousSystem)
	)

	if systemDelta > 0.0 && cpuDelta > 0.0 {
		cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
	}
	return cpuPercent
}

func calculateMemPercentUnixNoCache(limit, usedNoCache uint64) float64 {
	// MemoryStats.Limit will never be 0 unless the container is not running and we haven't
	// got any data from cgroup
	if limit != 0 {
		return float64(usedNoCache) / float64(limit) * 100.0
	}
	return 0
}
